﻿using System;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Net;
using System.Net.Sockets;

namespace WeatherRetriever
{
    public class WeatherGetter
    {
        private string city;
        private TcpClient client;
        private Weather result;
        private bool locked;

        private WeatherGetter(string eCity, TcpClient cli)
        {
            city = eCity;
            client = cli;
            result = null;
            locked = false;
        }

        public static Weather GetWeather(string city)
        {
            WeatherGetter g = CreateNew(city);
            return GetWeather(g);
        }

        public static Weather GetWeather()
        {
            WeatherGetter g = CreateNew();
            return GetWeather(g);
        }

        private static Weather GetWeather(WeatherGetter g)
        {
            g.Get();
            if (g.HasResult())
                return g.Result;
            else
                throw new IOException("An unknown exception occurred in WeatherGetter.Get()");
        }

        public void Get()
        {
            Get(WeatherRetrievalSettings.Default);
        }

        internal void Get(WeatherRetrievalSettings settings)
        {
            if (Locked)
                throw new InvalidOperationException("Cannot call Get() while instance is locked");
            if (client.Connected)
                throw new SystemException("Instance variable 'client' is already connected");

            // Lock the instance for thread-safety and set up initial conditions
            locked = true;
            string xmlWeatherData;
            Regex pattern = new Regex(settings.weatherRegexp);
            Match match;
            GroupCollection groups;
            result = new Weather();

            // Retrieve the data, making sure we close the socket afterwards
            client.Connect(settings.weatherHost, settings.weatherPort);
            try
            {
                WriteRequest(client.GetStream(), settings.weatherBaseURL + city, settings.weatherHost);
                WaitForReply(client, settings.sleepDelay);
                xmlWeatherData = ReadFromStream(client.GetStream(), settings.readBufferSize);
            }
            finally
            {
                client.Close();
                locked = false;
            }

            // Update reference variables for the sake of readability
            match = pattern.Match(xmlWeatherData);
            groups = match.Groups;

            if (match.Success)
            {
                // Update information appropriate in the result field
                result.Condition = groups["skytext"].Value;
                result.Date = DateTime.Now;
                result.FeelsLike = new FahrenheitTemp(groups["feelslike"].Value);
                result.Humidity = byte.Parse(groups["humidity"].Value);
                result.Temperature = new FahrenheitTemp(groups["temp"].Value);
                result.WindCondition = new Weather.Wind(byte.Parse(groups["windspeed"].Value),
                    Weather.Wind.StringToDirection(groups["winddirection"].Value));
            }
            else
                throw new InvalidOperationException("Could not retrieve data for the specified city, or regexp parsing failed");
        }

        private static string ReadFromStream(NetworkStream stream, int bufferSize)
        {
            string read;
            byte[] buffer = new byte[bufferSize];
            int bytesRead = stream.Read(buffer, 0, bufferSize);
            read = Encoding.ASCII.GetString(buffer);

            return read;
        }

        private static void WaitForReply(TcpClient client, byte sleepDelay)
        {
            while (client.Available == 0)
                System.Threading.Thread.Sleep(sleepDelay);
        }

        private static void WriteRequest(NetworkStream stream, string url, string host)
        {
            if (!stream.CanWrite)
                throw new IOException("stream.CanWrite returned false");
            string request = "GET " + url + " HTTP/1.1\r\nHost: " + host + "\r\nConnection: Keep-Alive\r\n\r\n";
            byte[] buffer = Encoding.ASCII.GetBytes(request);
            stream.Write(buffer,0,buffer.Length);
        }

        public bool HasResult()
        {
            return (result != null);
        }

        public bool Locked
        {
            get { return locked; }
        }

        public Weather Result
        {
            get { return result; }
        }

        public string City
        {
            get { return city; }
            set
            {
                if (Locked)
                    throw new InvalidOperationException("Cannot change the value of City while instance is locked");
                else
                    city = value;
            }
        }

        public static WeatherGetter CreateNew()
        {
            return CreateNew(WeatherRetrievalSettings.Default.collegeStation);
        }

        public static WeatherGetter CreateNew(string city)
        {
            return new WeatherGetter(city, new TcpClient());
        }
    }
}
